function sorted_list = fuf (folders,reclev,opt)
%FUF recursively retrieves files under specified folder(s).
%   
%   Syntax: sorted_list = fuf (folders,opt)
%
%   INPUT ARGUMENTS
%       - folders       can be a character array or a cell array of strings. Valid strings
%                       are matlab path;  
%       - opt           {'normal'} | 'detail'  is an optional field: when it's set to 'detail',
%                       FUF returns the full path of files searched for, while, when set to
%                       normal, it returns file names only.
%       - reclev        {1} optional scalar/numeric array  of  booleans: 1 stands for recursive
%                       search, whilst 0 limit the search to the specified folder.
%                       reclev can be either a scalar, or a 1 X length(folders) array.
%                       Use the 2nd option to specify the search rule on a folder-basis.
%                       Default is 1, i.d., each folder is searched for recursively.
%                       Setting reclev to 2 allows the routine to be called
%                       from a subroutine.
%
%
%   OUTPUT ARGUMENT
%       - sorted_list   is a sorted cell array of strings containing the name or the full path
%                       of files recursively found under the given folders.
%
%   FUF applies the wildcard * for searching files through all the directories beneath the given folder(s)
%   if the reclev parameter is set to 1. If you want to have different wildcards for folders belonging
%   to the same tree root, you need to pass them separately as input arguments.
%
%   REMARKS
%       - Folders don't have to belong to the same directory, therefore this function doesn't
%         have to be invoked from a particular directory.
%       - Matlab partial paths are not valid input arguments.
%       - Wildcard * can be used to narrow the search.
%       - To get the full path of a given file, FUF doesn't use the which command,
%         therefore it normally works also with java function that are not loaded.
%       - After completion, the working directory is set to the current directory at the time
%         of the function call.
%
%   EXAMPLES
%
%   To retrieve all files under the folder Utils:
%
%       >>  dir('C:\matlabR12\work\Interface\Utils')
%
%           .                       Contents.m              
%           ..                      my_interface_evalmcw.m  
%
%   To retrieve recursively the full path of all .m files starting with Cont under the folder work:
%
%       >> fuf('C:\matlabR12\work\Con*.m','detail')
%
%           ans =
%
%           'C:\matlabR12\work\Laboratorio_modellistica\DTT\Contents.m'
%           'C:\matlabR12\work\Laboratorio_modellistica\SDF\Contents.m'
%           'C:\matlabR12\work\PREPOSTGUIS\Contents.m'
%           'C:\matlabR12\work\PREPOSTGUIS\Utils\Contents.m'
%           'C:\matlabR12\work\Pavia\Contents.m'
%           'C:\matlabR12\work\TwoLe_front_end\Contents.m'
%           [1x74 char]
%           'C:\matlabR12\work\TwoLe_front_end\My_Classes\@Sector\Contents.m'
%           'C:\matlabR12\work\TwoLe_front_end\Utils\Contents.m'
%           'C:\matlabR12\work\Utils\Contents.m'
%
%   To retrieve recursively under the folder PREPOSTGUIS all .fig files and non recursively, under
%   the folder MatlabR12 all .txt files:
%
%       >> fuf({'C:\matlabR12\work\PREPOSTGUIS\*.fig','C:\matlabR12\*.txt'},[1,0],'detail')
%       
%           ans =
%
%               'C:\matlabR12\license.txt'
%               'C:\matlabR12\work\PREPOSTGUIS\Private\MV_Manager.fig'
%               'C:\matlabR12\work\PREPOSTGUIS\R_h.fig'
%               'C:\matlabR12\work\PREPOSTGUIS\TSA.fig'
%               'C:\matlabR12\work\PREPOSTGUIS\Visual_3D.fig'       

%                                         -$-$-$-
%
%  Author:       Francesco di Pierro        Dep. of Electronics and Computer Science (DEI)
%                                           Politecnico di Milano
%                                           e-mail: dipierro@elet.polimi.it
%
%                                         -$-$-$-

%-------------------------------GLOBAL VARIABLE DECLARATION----------------------------------%

global INTERM_LIST

%--------------------CHECK INPUT ARGUMENT TYPE AND SET DEFAULT VALUES------------------------%

%get the level of recursion
ST = dbstack;
if(reclev == 2)
    ST = 1;
    reclev = 1; % cheat!
end;
%if the level of recursion is 1, that is this is the first time the function was called, check 
%the argument type
if size(ST,1)==1
    INITIAL_DIR = pwd;
    error(nargchk(1,3,nargin));
    if nargin==1
        if ischar(folders)
            folders = cellstr(folders);
        elseif ~iscellstr(folders)
            error('The 1st argument to the function fuf must be either a string or a cellstring!')
        end
        reclev = ones(size(folders));
        opt = 'normal';
    elseif nargin==2
        if ischar(folders)
            folders = cellstr(folders);
        elseif ~iscellstr(folders)
            error('The 1st argument to the function fuf must be either a string or a cellstring!')
        end
        if isnumeric(reclev)
            if ~all(ismember(reclev,[0,1]))
                error('The 2nd argument to the function  must be a scalar/vector of booleans!')
            end
            if prod(size(reclev))==1
                reclev = reclev*ones(size(folders));
            elseif prod(size(reclev))~=prod(size(folders))
                error('The 2nd argument to the function must be a either a scalar or a 1 X length(1st argument) array!')
            end
            opt = 'normal';
        elseif ~ischar(reclev) | ~any(strcmp(reclev,{'normal','detail'}))
            error('Mismatched argument specification; the 3rd argument can be either set to ''normal'' or ''detail''');
        else
            opt = reclev;
            reclev = ones(size(folders));
        end
    else
        if ischar(folders)
            folders = cellstr(folders);
        elseif ~iscellstr(folders)
            error('The 1st argument to the function fuf must be either a string or a cellstring!')
        end
        if isnumeric(reclev)
            if ~all(ismember(reclev,[0,1]))
                error('The 2nd argument to the function must be a scalar/vector of booleans!')
            end
            if prod(size(reclev))==1
                reclev = reclev*ones(size(folders));
            elseif prod(size(reclev))~=prod(size(folders))
                error('The 2nd argument to the function must be a either a scalar or a 1 X length(1st argument) array!')
            end
        else
            error('The 2nd argument to the function must be a scalar/vector of booleans!')
        end
        if ~ischar(opt) | ~any(strcmp(opt,{'normal','detail'}))
            error('Mismatched argument specification: Tte 3rd argument to the function must be either set to ''normal'' or ''detail''');
        end
    end
    EXIT = 0;
    for i=1:length(folders)                                 %scan folders searching for incorrect folder
        [d,f,e] = fileparts(folders{i});                    %names and partialpaths!!!
        if (~isdir(d) & ~isempty(e))
            warning(['"',d,'" is not a valid folder name!'])
            EXIT = 1;
        elseif (~isdir(fullfile(d,f,'')) & isempty(e))
            warning(['"',fullfile(d,f,''),'" is not a valid folder name!'])
            EXIT = 1;
        elseif (isempty(dir(d)) & ~isempty(e)) | (isempty(dir(fullfile(d,f,''))) & isempty(e))
            warning('Matlab PARTIALPATHS not allowed!') 
            EXIT = 1;
        else                                                %this cheks the very unlike event arising
            sub = dir(pwd);                                 %when the partial path provided is a directory
            [subel{1:length(sub)}] = deal(sub.name);        %under the current one: this, in fact, is the
            [subtype{1:length(sub)}] = deal(sub.isdir);     %only situation where the dir command handles 
            testel1 = strcmp(subel,fullfile(d,f,''));       %partialpaths!!!
            testel2 = strcmp(subel,d);
            if ~isempty(subtype(testel1)) | ~isempty(subtype(testel2))
                warning('Matlab PARTIALPATHS not allowed!')
                EXIT = 1;
            end
        end
    end    
    if EXIT 
        disp('')
        error(strvcat('One or more invalid folder names encountered! Function aborted!'))
    end
end

%--------------------------------END ARGUMENT CHECKING--------------------------------%

wild = '';                                  %is the search condition
for i=1:length(folders)
    [pth,fname,ext] = fileparts(folders{i});
    clear cnval1 cnval2 cdval1 cdval2
    cd(pth);                                %move to the directory: isdir only recognizes directories on the Matlab search path or the current one!
    if ~isdir(fname)                        %if a wild card is used...
        val1 = dir(folders{i});             %get the filenames satisfying the search condition
        wild = [fname,ext];
        if ~isempty(val1)
            [cnval1{1:length(val1)}] = deal(val1.name); [cdval1{1:length(val1)}] = deal(val1.isdir);
        else
            cnval1 = [];    cdval1 = [];
        end
        val2 = dir(pth);                    %and the directory/ies under the current one
        if ~isempty(val2)
            wo = NaN*ones(size(val2));
            for k=1:length(val2)
                if ~val2(k).isdir
                    wo(k) = k;
                end
            end
            val2(~isnan(wo)) = [];
            [cnval2{1:length(val2)}] = deal(val2.name); [cdval2{1:length(val2)}] = deal(val2.isdir);
        else
            cnval2 = [];    cdval2 = [];
        end
        cnval = [cnval1,cnval2];    cdval = [cdval1,cdval2];    %and buiild the new search structure
        [val(1:length(cnval)).name] = deal(cnval{:}); [val(1:length(cdval)).isdir] = deal(cdval{:}); 
    else
        val = dir(folders{i});
        pth = fullfile(pth,fname);
    end
    for j=1:length(val)
        if (val(j).isdir) & not(strcmp(val(j).name,'.')) & not(strcmp(val(j).name,'..')) & not(val(j).name(1) == '.')    %if the jth object under the ith folder is a valid folder name(directory)...
            
            new_path = fullfile(pth,val(j).name);                                           %set the current directory to that one:
            cd(new_path);                           %recursively call the function keeping in mind the search condition
            if size(ST,1)==1 
                if reclev(i)
                    fuf({[new_path,filesep,wild]},reclev(i),opt);
                end
            elseif reclev
                fuf({[new_path,filesep,wild]},reclev,opt);
            end
            cd ..           %get back to the previous directory                            
        elseif not(strcmp(val(j).name,'.')) & not(strcmp(val(j).name,'..')) %if the jth object under the ith folder is a valid file name
            cd(pth);        %append it to the end of the list & get back to the previous directory
            if strcmp(opt,'detail')
                INTERM_LIST =[INTERM_LIST; cellstr(fullfile(pwd,val(j).name))];
            else
                INTERM_LIST =[INTERM_LIST; cellstr(val(j).name)];
            end
        end
    end
end

sorted_list = sortrows (INTERM_LIST);       %return the sorted list of files

if size(ST,1)==1                %if the level of recursion is 1, that is this is the last time that the next lines are executed
    clear global INTERM_LIST;   %clear the global variable
    cd(INITIAL_DIR);            %and get back to the initial dir
else
    clear INTERM_LIST;          %clear the global variable link
end